import { getPushToken } from "../tokens";
import { LogCollection } from "./LogCollection";
import { TraceCollection } from "./TraceCollection";
import { TraceStackFrame } from "./TraceStackFrame";

export function createLogStack() {
    return new LogCollection();
}

/**
 * @description: pushplus 的令牌。用于发送日志。
 */
let _token: string | null = null;

/**
 * @description: 通过抛出异常，从调用堆栈中获取调用者的函数名。
 * @param {number} [index] 调用堆栈层数（默认为 0 ），大于等于 0 视为 0 即调用此函数的函数名。
 * @return {string} 调用者的函数名。
 */
export function getCallerName(index: number = 0): string {
    let trace = sliceStackFrames(getRawStackTrace(), 1, 0);
    let stackFrames = parseTrace(trace);

    // 检查参数 index 的范围
    if (index < 0) index = 0;
    if (index > stackFrames.length - 1) index = stackFrames.length - 1;

    return stackFrames[index].getCallerName();
}

/**
 * @description: 获取当前真实的调用堆栈。
 * @param {Function} [endFunction] 终止栈帧，会自动排除后续的无用栈帧。
 * 
 * **注意！：**
 * 
 * - 匿名函数和类中的方法等 `console.trace()` 方法不显示的函数不能当作终止栈帧。
 * 
 * @return {string} 调用堆栈的字符串。
 */
export function getRawStackTrace(endFunction?: Function): string {
    let stackTrace = { stack: '' };

    Error.captureStackTrace(
        stackTrace,
        endFunction
    );

    // 删除不必要的栈帧
    return sliceStackFrames(stackTrace.stack, 1, -2);
}

/**
 * @description: 获取修正后的调用堆栈信息。
 * @param {Function} [endFunction] 终止栈帧，会自动排除后续的无用栈帧。
 * @return {TraceCollection} 调用堆栈集合。
 */
export function getStackTrace(endFunction?: Function): TraceCollection {
    let trace = sliceStackFrames(getRawStackTrace(endFunction), 1, 0);
    return new TraceCollection(...parseTrace(trace));
}

/**
 * @description: 设置 pushplus 的令牌，必须为 32 位十六进制数的字符串。
 * 
 * 如果不是运行中获取的令牌，可以选择在脚本配置文件当中添加如下名为 `TOKEN` 的字段，在读取全局变量时会自动加载。
 * 
 * @param {string} token 用于调用 pushplus api 的令牌。
 * @return {boolean} 是否设置成功。
 */
export function setToken(token: string): boolean {
    if (token.length !== 32 || /^\d*$/.test(token)) {
        return false;
    }

    _token = token;

    return true;
}

/**
 * @description: 将信息发送到远程，并带上换行符，返回是否发送成功。
 * 
 * 可以一次性传入多个参数，第一个参数作为主要信息，其他参数作为类似于 [printf(3)](https://man7.org/linux/man-pages/man3/printf.3.html) 中的代替值（参数都会传给 `util.format()` ）。
 * @param {string} title 发送消息的标题。
 * @param {string} data 主要信息。
 * @param {array} [args] 要填充的数据。
 * @return {boolean} 是否发送成功。
 */
export function sendMessage(title: string, data: string, ...args: any[]): boolean {
    // @ts-ignore
    data = util.format(data, ...args);
    return sendToRemote(title, data);
}

/**
 * @description: 使用 pushplus 发送日志集合。
 * @param {LogCollection} [logs] 要发送的日志集合，默认发送完整的日志堆栈。可以通过过滤方法，选择性发送。
 * @param {string} [title] 发送消息的标题（默认为 `logger` ）。
 * @param {boolean} [clear] 发送后是否清空日志堆栈（默认为 `true` ）。
 * 
 * **注意！：**
 * 
 * - 发送失败时并不会清空日志堆栈，不管 `clear` 参数为何值。
 * - 在选择的日志集合不为默认时需要手动清除全部日志。
 * 
 * @return {boolean} 是否发送成功。
 * @example
 * ```typescript
 * // 只发送全部 log 等级的日志
 * let collection = logStack.filter((frame) => {
 *     return frame.getLevel() == LogLevel.log;
 * });
 * sendLog(collection);
 * logStack.clear();
 * ```
 */
export function sendLog(logs: LogCollection, title?: string, clear?: boolean): boolean {
    title = title ?? 'logger';
    clear = clear ?? true;
    let isSend = sendToRemote(title, logs.toHtmlString());

    if (isSend && clear) {
        logs.clear();
    }

    return isSend;
}

/**
 * @description: 截取需要的栈帧。
 * @param {string} stackTrace 调用堆栈字符串。
 * @param {number} start 开始行（默认为 0 ），小于等于 0 时视为 0 。
 * @param {number} end 结束行（默认为 0 ），为正时表示从前计数，为负时表示从后计数。
 * @return {string} 处理后的调用堆栈字符串。
 */
export function sliceStackFrames(stackTrace: string, start: number = 0, end: number = 0): string {
    if (stackTrace === '') return '';

    let temp = stackTrace.split('\n');

    // 映射负值
    if (end <= 0) end = temp.length + end;

    // 检查参数 start 的范围
    if (start < 0) {
        start = 0;
    } else if (start > temp.length - 1) {
        start = temp.length - 1;
    }

    // 检查参数 end 的范围
    if (end > temp.length) {
        end = temp.length;
    }
    else if (end <= start) {
        return '';
    }

    temp = temp.slice(start, end);
    return temp.join('\n');
}

/**
 * @description: 格式化调用堆栈信息。
 * @param {string} originTrace 原始调用堆栈字符串（使用异常的 `stack` 属性取得）。
 * @return {TraceStackFrame[]} 格式化后的调用堆栈数据。
 */
export function parseTrace(originTrace: string): TraceStackFrame[] {
    let stack: TraceStackFrame[] = [];
    let originStack: string[] = originTrace.split('\n');

    for (let item of originStack) {
        let result = /\:(\d+)(?: \((.*)\))?/.exec(item)!;
        stack.push(new TraceStackFrame(
            // 修正和源码偏移的行数
            Number(result[1]) - 3,
            result[2] ?? 'Anonymous functions'
        ));
    }

    // 修改最外层调用名称为 Outer
    stack[stack.length - 1].setCallerName("Outer");

    return stack;
}

/**
 * @description: 使用 pushplus 推送文本。
 * @param {string} title 发送消息的标题。
 * @param {string} message 要发送的消息。
 * @return {boolean} 是否发送成功。
 */
function sendToRemote(title: string, message: string): boolean {
    // TODO: 抛出异常？
    if (_token === null) {
        _token = getPushToken()
    }

    if (!_token) {
        return false;
    }

    let res = http.post(`http://www.pushplus.plus/send`, {
        title: title,
        token: _token,
        content: message,
        template: 'html'
    })
    if (!res) return false;
    return res.statusCode === 200;
}
